/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package semant.second_pass;

import semant.Env;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.Symbol;
import symbol.VarInfo;
import syntaxtree.ArrayAssign;
import syntaxtree.Assign;
import syntaxtree.Block;
import syntaxtree.BooleanType;
import syntaxtree.If;
import syntaxtree.IntegerType;
import syntaxtree.Print;
import syntaxtree.Statement;
import syntaxtree.Type;
import syntaxtree.VisitorAdapter;
import syntaxtree.While;
import util.List;

class StatementHandler extends VisitorAdapter {

    private final Env env;
    private final ClassInfo cinfo;
    private final MethodInfo minfo;

    private StatementHandler(Env e, ClassInfo cl, MethodInfo mt) {
        env = e;
        cinfo = cl;
        minfo = mt;
    }

    static void secondPass(Env env, ClassInfo cl, MethodInfo mt, Statement s) {
        StatementHandler h = new StatementHandler(env, cl, mt);
        s.accept(h);
    }

    @Override
    public void visit(Block node) {
        for (List<Statement> v = node.body; v != null; v = v.tail) {
            StatementHandler.secondPass(env, cinfo, minfo, v.head);
        }
    }

    @Override
    public void visit(If node) {
        // {if} [token]:if [condition]:exp [then_clause]:statement [else_clause]:statement?
        // cond tem que ser boolean
        // else eh opcional

        Type condType = ExpHandler.secondPass(env, cinfo, minfo, node.condition);

        if (!(condType instanceof BooleanType)) {
            env.err.Error(node, new Object[]{"While sem boolean"});
        }

        StatementHandler.secondPass(env, cinfo, minfo, node.thenClause);

        if (node.elseClause != null) {
            StatementHandler.secondPass(env, cinfo, minfo, node.elseClause);
        }
    }

    @Override
    public void visit(While node) {
        // {while} [token]:while [condition]:exp [body]:statement
        // cond precisa ser boolean

        Type condType = ExpHandler.secondPass(env, cinfo, minfo, node.condition);

        if (!(condType instanceof BooleanType)) {
            env.err.Error(node, new Object[]{"While sem boolean"});
        }

        StatementHandler.secondPass(env, cinfo, minfo, node.body);

    }

    @Override
    public void visit(Print node) {
        // {print} [token]:print [value]:exp
        // exp precisa ser int

        Type indexType = ExpHandler.secondPass(env, cinfo, minfo, node.exp);

        if (!(indexType instanceof IntegerType)) {
            env.err.Error(node, new Object[]{"Print em variavel nao inteira."});
        }
        
    }

    @Override
    public void visit(Assign node) {
        // {assign} [target]:id [value]:exp
        // id tem que estar declarado
        // tipo exp tem que ser compativel com o de id

        Symbol varName = Symbol.symbol(node.var.s);
        VarInfo v = StatementHandler.getVariable(cinfo, minfo, varName);

        if (v == null) {
            env.err.Error(node, new Object[]{"Variavel nao declarada: ", varName});
        }
        else
        {
            Type condType = ExpHandler.secondPass(env, cinfo, minfo, node.exp);
            if (!TypeHandler.compatible(env, v.type, condType)) {
                env.err.Error(node, new Object[]{"Tipos incompativeis em atribuicao"});
            }
        }

    }

    @Override
    public void visit(ArrayAssign node) {
        // {array_assign} [target]:id [index]:exp [value]:exp
        // id tem que estar declarado
        // index tem que ser Integer
        // exp precisa ser compativel com o tipo de id

        Symbol varName = Symbol.symbol(node.var.s);
        VarInfo v = StatementHandler.getVariable(cinfo, minfo, varName);

        if (v == null) {
            env.err.Error(node, new Object[]{"Array nao declarada: ", varName});
        }
        else
        {
            Type indexType = ExpHandler.secondPass(env, cinfo, minfo, node.index);

            if (!(indexType instanceof IntegerType)) {
                env.err.Error(node, new Object[]{"Tipo do array incompativel com o da expressao"});
            }

            Type expType = ExpHandler.secondPass(env, cinfo, minfo, node.value);

            if (!TypeHandler.compatible(env, new IntegerType(node.line, node.row), expType)) {
                env.err.Error(
                		node, 
                		new Object[]{
                				"Tipos incompativeis na atribuicao de array",
                				"Esperado: int",
                				"Encontrado: " + expType
                				});
            }
        }

    }

    private static VarInfo getVariable(ClassInfo cinfo, MethodInfo minfo, Symbol varName) {

        // procura nos atributos da classe
        VarInfo vinfo = cinfo.attributes.get(varName);
        
        if (vinfo != null) {
            return vinfo;
        }

        // procura nos parametros dos metodo
        vinfo = minfo.formalsTable.get(varName);

        if (vinfo != null) {
            return vinfo;
        }

        // procura nas variaveis locais
        // se nao achar ateh aqui, tem que retornar null
        // TODO: buscar tambem na classe base
        return minfo.localsTable.get(varName);

    }
}
